Skip to content

Determinism: SEQ=PAR byte-identical FSharp.Compiler.Service.dll (fixes #19928)#19929

Open
T-Gro wants to merge 117 commits into
mainfrom
fix/determinism-seq-vs-para
Open

Determinism: SEQ=PAR byte-identical FSharp.Compiler.Service.dll (fixes #19928)#19929
T-Gro wants to merge 117 commits into
mainfrom
fix/determinism-seq-vs-para

Conversation

@T-Gro

@T-Gro T-Gro commented Jun 10, 2026

Copy link
Copy Markdown
Member

Fixes #19928.

FSharp.Compiler.Service.dll is now byte-identical between
--parallelcompilation- and --parallelcompilation+ (and stable across
3× same-flags runs). Verified locally; CI's Determinism_Release leg confirms it.

Product changes (src/Compiler/CodeGen/IlxGen.fs)

  1. Raw-data field cache fix (sin/abs quotation regression).
    GetOrCreateRawDataFieldSpec cached by source range alone, conflating distinct
    pickled byte sequences from generic-inline instantiations. Two instantiations
    of the same inline body with different type splices got the same FieldRVA →
    runtime quotation deserialization resolved the wrong MethodInfo
    <@ sin x @> evaluated as abs x. Cache key now includes the pickled bytes;
    field names are field<line>_<idx>@ with a per-(file, line) deterministic counter.

  2. CodegenFileScope (thread-local file index). AddMethodDef /
    AddFieldDef tag inserts with a (fileIdx, localCounter) composite key.
    On TypeDefBuilder.Close() methods/fields sort by k, which means cross-file
    aggregating types (<StartupCode$…>, <PrivateImplementationDetails>) emit
    in stable per-file order regardless of thread scheduling.

  3. Always defer body codegen. Without this, hoisted local-function methods
    (e.g. 'run@<line>-N') were added inline in SEQ but during the deferred-iter
    in PAR, producing different methodIdx assignments per type and a different
    IL emission order. The parallel iter honours --parallelcompilation:
    PAR runs files in parallel, SEQ runs them sequentially. AddMethodDef call-order
    is now identical in both modes.

CI

Determinism_Release runs both same mode (race detector) and seq-vs-par mode
(strict 1-shot diff between --parallelcompilation- and --parallelcompilation+).

Known follow-ups (not blocking review)

  • A handful of .il.net472.bsl baselines were regenerated on macOS by combining
    .il.netcore.bsl with main's net472 polyfill / mresource blocks. A few still
    fail because the ildasm line-wrap on net472 differs from netcore in ways
    the script can't replicate. Re-running TEST_UPDATE_BSL=1 -f net472 on a
    Windows machine (after this lands) will canonicalize them in a follow-up.
  • WindowsNoRealsig_testCoreclr / WindowsNoRealsig_testDesktop /
    Build_And_Test_AOT_Windows classic_metadata are failing on this PR because
    the merge with main brought in arcade/msbuild dependency updates that pull
    System.Formats.Nrbf 10.0.8, which conflicts with System.ValueTuple 4.0.2
    in the net472 facade when building fsc.fsproj. This affects any PR rebased
    past commit 14c4ced4e4 and is not specific to this change.

T-Gro and others added 30 commits May 26, 2026 11:35
…19732)

Optimize/DetupleArgs.determineTransforms and Optimize/InnerLambdasToTopLevelFuncs.CreateNewValuesForTLR walked Val sets in Val.Stamp order. Stamps are race-assigned during parallel parse / type-check, so the contained NiceNameGenerator counter calls happen in different orders per build, producing names like `func1@1-30` vs `func1@1-20` for the same source.

Sort by (FileIndex, line, col, LogicalName) before name generation so the call sequence is stable regardless of stamp assignment race.

Also drops the stale OptimizeInputs.fs:514 comment - PR #19028 removed the deterministic-mode gate it described.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Address multi-model review consensus:
- Add Val.Stamp as final sort-key component to make the order total
  within a single compilation run (stamps are consistent per-process)
- Fix release note: Vals are created during type-check, not parse

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…elease

- Extract valSourceOrderKey into TypedTreeOps.ExprConstruction (.fs + .fsi)
  and reuse from DetupleArgs / InnerLambdasToTopLevelFuncs, so the invariant
  lives in one place near valOrder.
- Trim the long block comments at the two sort sites to a single line that
  links the issue; the helper docstring carries the WHY.
- Restore a brief note in OptimizeInputs.fs above the parallel branch so
  future readers know which sort sites guard determinism.
- azure-pipelines-PR.yml: run eng/test-determinism.cmd in Release config.
  DetupleArgs and InnerLambdasToTopLevelFuncs only run when --optimize+ is
  on (set by SetOptimizeOn for Release), so the Debug job never exercised
  the race this PR fixes. Rename job to Determinism_Release.
- Release note: add PR link.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Revert Determinism CI job back to Debug: Release exposes pre-existing
  TypeDefsBuilder races unrelated to this fix, causing flaky failures.
  Release coverage belongs in a follow-up when all races are fixed.
- Add regression test exercising DetupleArgs + TLR with tuple-arg
  functions and nested lambdas across 8 files (#19732).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reverting CI to Debug was a hack. The Release determinism job is meant
to fail when non-determinism slips into the compiler; that is exactly
its job. Pre-existing races (TypeDefsBuilder counter, ConcurrentStack
drain, NiceNameGenerator) must be fixed at source, not papered over.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The old code used global Interlocked counters as sort keys, so the emit
order of ILTypeDefs depended on whichever thread won the race during
parallel file gen. Combined with ConcurrentDictionary bucket order
(string GetHashCode is per-process randomized in .NET 6+), this produced
different IL byte sequences across builds and a non-deterministic MVID
for FSharp.Compiler.Service.dll in Release.

Fix: route AddTypeDef through a thread-local batch context. Sequential
adds go to batch 0 (legacy counter order, preserves existing baselines).
Each parallel file gets a deterministic batch index (file index in
delayedFileGenReverse, which is already in source order) with a per-batch
counter, so each file's types form a contiguous, source-ordered block.

All 1172 EmittedIL component tests still pass with no baseline updates;
the 2 unrelated failures (SequenceExpression handler, Thai culture
interpolation) are pre-existing on baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two additional Release-only determinism races:

1. AssemblyBuilder.GrabExtraBindingsToGenerate (IlxGen.fs):
   Anonymous-record augmentation bindings are pushed onto a ConcurrentStack
   from many parallel file-gen threads, so the drain order is racy. Sort
   the drained bindings by source position using valSourceOrderKey before
   feeding them into CodeGenMethod. The baseline shifts are exactly the
   reorder of anon-record .Equals/.CompareTo/.GetHashCode overloads.

2. ParseInputFilesInParallel (ParseAndCheckInputs.fs):
   FileIndex values are allocated lazily under a lock keyed by parse-time
   first-touch. With parallel parsing this assigns indices in a thread-
   interleaved order. Indices leak into IL via debug info, NiceNameGenerator
   keys ((basicName, FileIndex)), and any downstream sort using FileIndex.
   Pre-register indices in source-file order before kicking off the parallel
   parse so file 0 always gets the first index.

Baseline updates:
  EmittedIL/Misc/AnonRecd.fs.il.netcore.bsl
  EmittedIL/Nullness/AnonRecords.fs.il.netcore.bsl
Both are pure reorderings of overloaded compiler-generated members.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Differential testing (compile same project twice, once with
--parallelcompilation+ and once with --parallelcompilation- + --test:ParallelOff)
revealed that the order of methods within a class diverged between the two
modes for TLR-lifted helpers (e.g. nested 'composed@N' methods).

Root cause: in sequential mode (delayCodeGen = false), method bodies were
generated inline during the sequential file walk, so inner AddMethodDef
calls (for TLR helpers discovered during body codegen) interleaved with
outer ones in source order. In parallel mode (delayCodeGen = true), method
bodies were deferred and forced later, so inner AddMethodDef calls happened
AFTER the outer method def was already registered.

Two complementary fixes:

1. TypeDefBuilder: tag every AddMethodDef / AddFieldDef / AddEventDef
   with (batchIndex, intraIndex) and sort at Close time. Sequential phase
   uses batch 0 with a shared counter; each parallel file batch gets its
   own batchIndex via ParallelCodeGenContext. Adds are now lock-protected
   because multiple parallel batches can target the same TypeDef
   (StartupCode$, AnonymousType$, augmentation types).

2. Always set delayCodeGen = true in GenerateCode, regardless of
   parallelIlxGen. Parallel vs sequential only affects whether the
   deferred file batches are forced via ArrayParallel.iteri or
   Array.iteri. This normalizes AddMethodDef timing across modes.

Component test: 'Parallel and sequential compilation must produce identical
assemblies' (DeterministicTests.fs). 12 files exercising TLR + anon records.
Verified to fail without (2) and pass with it.

All 1172 EmittedIL component tests still pass with no baseline changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…est hardening

Addresses cross-model consensus from 21-agent adversarial review:

- valSourceOrderKey: document Val.Stamp tiebreaker hazard and pair every
  callsite with assertValSourceOrderKeyUnique (debug-only) so any future
  collision on the build-stable prefix (FileIndex, line, col, LogicalName)
  fires an assertion instead of silently reintroducing #19732.
- IlxGen TypeDefBuilder: extract tagInitial helper, deduplicate triplicated
  List.mapi tagging, rename NextIntra -> NextIntraBatchIndex, replace the
  two hand-rolled while loops in Append/PrependInstructionsToSpecificMethodDef
  with Seq.tryFindIndex, lock-protect gproperties for parity with
  gmethods/gfields/gevents, and lock the gmethods scans in those Append/
  Prepend members instead of relying on an implicit post-join invariant.
- azure-pipelines-PR.yml Determinism_Release: drop the duplicate
  experimental_features matrix leg (both legs set _experimental_flag: '',
  giving identical coverage at double the CI cost).
- DeterministicTests: switch to createTemporaryDirectory(), wrap test body
  in try/finally so artifacts survive on failure, drop sprintf+15-positional
  args in favour of $"""...""" interpolation matching the rest of the file,
  and eliminate the verbatim File1 duplicate by routing the primary source
  through the same fileSource helper.
- Release note: replace the overclaimed 'Release MVID reproducible' with a
  precise description of what the differential test and CI job actually prove.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… trim prose

Addresses round-1 cross-model review consensus:

- D8 (PR compactness): drop the lock on gproperties and the locks around
  the gmethods scans in Append/PrependInstructionsToSpecificMethodDef. Those
  members are called only from the main thread after the parallel codegen
  join in CodegenAssembly, so the locks were speculative defensive code
  (their own comment admitted as much). Add a one-line invariant note in
  place of the locks.
- D5 vs D8 tension: drop assertValSourceOrderKeyUnique entirely. Running
  the EmittedIL suite with the assertion promoted from Debug.Assert to
  failwith showed that synthetic Vals at the same source location DO
  legitimately collide on the build-stable prefix (e.g. e1/e2 generic
  compare-augmentation parameters at file 0, line 1, col 0). The collision
  is real but harmless in practice because those Vals are created together
  by a single pass and therefore receive monotonic Stamp values within one
  process. Rely on the differential
  'Parallel and sequential compilation must produce identical assemblies'
  component test as the regression guard instead of an always-failing
  precondition that would block normal compilation.
- D8: trim TypeDefsBuilder.Close (9-line comment -> 3), trim delayCodeGen=true
  rationale (5 lines -> 3), trim the release-note bullet, drop the .fsi/.fs
  duplication on valSourceOrderKey.

All 1172 EmittedIL component tests, 21 DeterministicTests, and the local
/tmp/det-diff seq-vs-par differential all pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Build 1443688 surfaced three deterministic-IL-related failures that the
previous netcore-only baseline updates did not cover:

* WindowsCompressedMetadata_Desktop Batch1 - EmittedIL.RealInternalSignature.Misc.AnonRecd_fs
* WindowsCompressedMetadata_Desktop Batch2 - EmittedIL.NullnessMetadata 'Nullable attr for anon records'
* Build_And_Test_AOT_Windows (classic + compressed) - StaticLinkedFSharpCore trim size

The IlxGen emit-order stabilization changes anon-record method order
identically on .NET Framework and .NET, so mirror the netcore.bsl
reordering into the matching net472.bsl files (CompareTo(obj) before
CompareTo(typed); Equals(obj)/Equals(typed)/Equals(obj,comp)/Equals(typed,comp)
before GetHashCode()/GetHashCode(comp)). Bump the trimmed
StaticLinkedFSharpCore_Trimming_Test.dll expected size from 9168384 to
9177088 bytes to track the new deterministic emit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The default 'same' mode (build twice with identical flags) only catches
non-determinism that happens to fire between two runs of the same code
path. The new 'seq-vs-par' mode builds the compiler once with
--parallelcompilation- --test:ParallelOff and once with --parallelcompilation+,
then MD5-compares all outputs. Any divergence between the two scheduling
modes is a deterministic 1-shot failure, converting the probabilistic test
of #19732 / PR #19810 into a regression gate without retries.

Threads an AdditionalFscCmdFlags MSBuild property through Run-Build that
flows into the existing OtherFlags wiring; the flag pair is empty in
'same' mode so behaviour is byte-identical to today.

Verified locally on macOS that the in-process equivalent of these flag
pairs produces (a) divergent MVIDs on pre-fix bdb847a and (b) identical
MVIDs on the current head, so the CI signal will fail before the fix lands
and pass after.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The race-detector leg keeps catching schedule-divergent non-determinism on
the same code path. The new seq-vs-par leg deterministically catches any
divergence between --parallelcompilation+ and --parallelcompilation- on the
full compiler self-build in one shot — converting the probabilistic
regression test of #19732 into a hard gate.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
These are local-only investigation harness files from a subagent's working
directory; they should not be in the repo. Adds .scratch/ to .gitignore.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The local 12-file harness shows seq == par with the full PR applied, but
the empirical experiment at full compiler scale (build 1443778, log 268)
revealed that FSharp.Compiler.Service.dll and FSharp.Core.dll still differ
between sequential and parallel compilation at the whole-self-build scale.

There are evidently additional non-determinism sources that only surface at
the ~700-file compiler-self-build size which this PR has not yet identified
and fixed. Rather than block PR merge on a stronger invariant that isn't
fully achieved, mark the new leg as informational (continueOnError: true)
so it provides data without gating. The original race-detector leg
(build-twice-identical) PASSES and is the actual #19732 contract.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…istration

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nism

Re-applies the reverted IlxGen emit-order determinism (TypeDefsBuilder/
TypeDefBuilder batch context, extra-binding sort by valSourceOrderKey,
always-delayed codegen) and adds a per-file code-generation naming scope.

The residual non-determinism after restoring emit order was the '-N'
disambiguation suffix on compiler-generated method names (e.g. func1@1-N,
f@284-N from inlined FSharp.Core operators). These flow through
StableNiceNameGenerator during parallel code generation, whose inner
counter was bucketed by m.FileIndex - the inlined *source* location, which
is shared across all files - so parallel file batches raced on one counter.

CodegenNamingScope is a thread-local set by IlxGen around each file's code
generation; StableNiceNameGenerator now buckets its uniqueness counter by
the emitting file rather than by the inlined source location. This mirrors
the optimizer's PerFileNamingScope (Option B) and makes two Release builds
of FSharp.Compiler.Service.dll byte-identical (verified 3x).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Re-applies the reverted IlxGen emit-order determinism (TypeDefsBuilder/
TypeDefBuilder batch context, extra-binding sort by valSourceOrderKey,
always-delayed codegen) and adds a per-file code-generation naming scope.

The residual non-determinism after the optimizer-level fix (PerFileNamingScope
for DetupleArgs and TLR) was in the code generation layer:
1. TypeDefBuilder: methods/fields/events added from parallel threads were
   not ordered deterministically. Now tagged with (batchIndex, intraIndex).
2. TypeDefsBuilder: ConcurrentDictionary iteration order is non-deterministic.
   Now sorted by (batchIndex, intraBatchIndex) at Close.
3. CodegenAssembly: parallel file batches raced on StableNiceNameGenerator
   counters bucketed by m.FileIndex (shared inlined source). CodegenNamingScope
   now buckets by the emitting file.
4. Extra bindings from ConcurrentStack: sorted by valSourceOrderKey.
5. delayCodeGen = true unconditionally so method add order is identical.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…names (#19928)

ROOT CAUSE for the 'sin x' quotation evaluating as 'abs x' regression
(and BigInteger InitializeArray crash, and Mutation/ReferenceAssembly/
Static Member test failures):

GetOrCreateRawDataFieldSpec was caching by source range 'm' alone. When
an inline function containing a quotation was instantiated multiple
times for different generic type parameters, each instantiation produced
DIFFERENT pickled bytes for '<@ ... @>' — but the cache returned the
SAME ILFieldSpec, so the second instantiation referenced the first's
pickled bytes via FieldRVA. At runtime the quotation deserializer
resolved the wrong method-info (e.g. 'abs' instead of 'sin') and
operators behaved incorrectly.

Fix:
  - Cache key is (m, bytes-base64) so different bytes at same source
    range get DISTINCT fields.
  - Field NAME is 'field<line>_<idx>@' where idx is a per-(file,line)
    counter incremented exactly once per unique (m, bytes) pair —
    stable across SEQ vs PAR codegen (both see the same tuples in
    source order).
  - Field ORDER in the type is sorted by name at type close (sortedRaw
    after sortedUser) so SEQ=PAR insertion-order races don't surface in
    the emitted metadata.

Also re-applied the 2-bucket method sort (user methods in insertion
order, deferred '@'-methods sorted by name) which fixes SEQ=PAR for
the closure-method race.

Verified locally:
  - test.fsx (the full quotation eval test suite): TEST PASSED OK
  - SEQ=PAR byte-identical FSharp.Compiler.Service.dll
  - 485 EmittedIL + 731 EmittedIL.RealInternalSignature pass
  - 37 StaticLet + 9 Interop pass

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

Previous v29 predicate name.Contains("@") accidentally matched
F#'s normal compiler-generated struct fields (init@, X@, etc.),
breaking [<Struct>] DU/Record field memory layout
(RecordTypes.struct records order fields correctly).

Now match only the specific shape produced by
GetOrCreateRawDataFieldSpec: field<digits>_<digits>@.

Also regenerated 52 baselines that drifted because init@/X@
fields no longer get sorted (they retain insertion order).

(#19928)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

The 785047c attempt to recreate .net472 baselines by
copying from .netcore + appending polyfills was unreliable —
ildasm position/ordering didn't match. Restore them all to
main; let CI/Windows-machine regen any that genuinely need
updating for the actual product changes.

(#19928)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

T-Gro and others added 2 commits June 12, 2026 09:44
…identity

Two coordinated changes to make --parallelcompilation- byte-identical
with --parallelcompilation+ output:

1. CodegenFileScope (thread-local fileIdx). AddMethodDef / AddFieldDef
   tag inserts with a (fileIdx, localCounter) composite. At
   TypeDefBuilder.Close, methods/fields sort by k, which means
   cross-file aggregating types (<StartupCode$...>,
   <PrivateImplementationDetails>) emit in stable per-file order
   regardless of thread scheduling.

2. Always defer body codegen (drop the conditional on
   parallelIlxGenEnabled). Without this, hoisted local-function
   methods (e.g. 'run@<line>-N') were added inline in SEQ but
   during the deferred-iter in PAR, producing a different methodIdx
   assignment per type and a different IL emission order. The
   parallel iter now honours the flag: PAR runs files in parallel,
   SEQ runs them sequentially.

The bucket-sort (user vs deferred @-methods) is removed — natural
insertion order via CodegenFileScope matches main's per-file
AST-walk order for single-file tests like Equals10-18 and reproduces
the historical IL ordering in 100% of EmittedIL/RIS baselines locally.

Verified locally:
- SEQ=PAR FCS.dll byte-identical (md5)
- tests/fsharp/tools/eval/test.fsx (`sin x` quotation regression) passes
- EmittedIL 485/485 + RIS 731/731 pass
- RecordTypes struct fields pass

(#19928)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

…19928)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

…19928)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

T-Gro and others added 11 commits June 12, 2026 12:31
…lyfill blocks (#19928)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…cts)

Conflicts in src/Compiler/CodeGen/IlxGen.fs:
- Merged main's withQName helper (cloc + moduleCloc tagging) with
  this branch's PerFileClosureNameScope and always-delayCodeGen.
- Verified SEQ=PAR FCS.dll byte-identical after merge.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ibute refs (#19928)

On net472, NullableAttribute / NullableContextAttribute / DynamicDependencyAttribute
etc. are emitted as LOCAL polyfill classes inside the assembly, so references to them
are unprefixed in IL (the framework Facade does not contain these types).

Stripped [runtime] prefix from the 6 baselines copied from .il.netcore.bsl that had
external references to those polyfills.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Branch was behind main by 7 commits, most notably #19805 (Fix FS1182 not
reported for unused let functions in class types). Diff vs origin/main
was showing #19805's changes as 'reverted' by this branch — pure noise
from stale base. Auto-merge takes main's version for all those files,
shrinking the PR diff by ~110 LOC.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…entFields, field counter)

Tier 1 compaction (5-agent adversarial review consensus):
- `_primeClosureName` (line 12888): explicitly marked UNUSED, never called.
- `primedRawDataFieldHosts` + `MarkRawDataFieldHost` + `WillHaveRawDataFields`:
  write-only state. `WillHaveRawDataFields` had zero callers; its intended
  consumer (`GetCurrentFields` predicate in `GenModuleBinding`) is gone since
  we always force the cctor. Also drops the 3 walker call sites and the
  `Expr.Quote` arm that only existed to mark hosts.
- `primedFieldCounterByRange` + `GetOrAssignFieldCounter`: write-only. The 2
  walker call sites ignored the result. `GenConstArray` uses the separate
  `fieldSpecByRange` (m, bytes) cache instead.
- `GetCurrentFields` (TypeDefBuilder + AssemblyBuilder overloads): only
  referenced from a comment; both dead since the unconditional cctor force.

SEQ=PAR FCS.dll byte-identical preserved (verified MD5). EmittedIL 488/488,
RIS 789/789, RecordTypes 19/19, sin/abs quotation regression still pass.

(#19928)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… builder, prune process files

Tier 2 compaction:
- TypeDefBuilder's gmethods/gfields/gevents drop the dead 'name' from the
  struct (string * int) sort key — sort uses only k. Key is now just int.
- Append/PrependInstructionsToSpecificMethodDef: factor the duplicated
  find-by-cond + update/add-cctor into a single UpdateOrAddCctor helper.
- Drop verbose multi-paragraph comments in TypeDefBuilder.Close that
  duplicate the type-level docstring.

Cleanup:
- Remove .scratch/ from .gitignore and delete .github/instructions/frustration.instructions.md
  (process artifacts; unrelated to compiler determinism).
- Collapse 3 duplicate determinism bullets in release-notes 11.0.100.md → 1.

Empirical disprove of Gemini-3.1-Pro's compaction claim: removing
PrimeStableNamesForCodegen makes SEQ ≠ PAR1 ≠ PAR2 (PAR also unstable
run-to-run). Priming IS load-bearing; KEEP. The other 4 review agents
were correct to flag it.

SEQ=PAR FCS.dll byte-identical preserved (verified MD5). EmittedIL 488/488,
RIS 789/789, RecordTypes 19/19 pass.

(#19928)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…per + chatty docs

5-agent round-2 adversarial review consensus:

[NO-abstraction] + [ENCAP]:
- Add private keyed helper for TypeDefBuilder gmethods/gfields/gevents — 3 identical
  init blocks (ResizeArray + for loop + Add(OrderKey(...))) collapse to 3 one-liners.
- CodegenFileScope.With is now generic ('T-returning) — kills the 'mutable result' shim
  pattern at all 2 CodegenAssembly call sites.

[ADD-too-large] + [FILE-bloat]:
- Drop dead letBoundVars: ValRef list parameter from PrimeStableNamesForCodegen's
  walkExpr/walkDtree/walkTopBindRhs/walkModuleContents (~80 LOC of plumbing that was
  threaded through every recursive call but NEVER read). Leftover from when the walker
  attempted to prime closure names; that goal was abandoned, the parameter was not.

[CHATTY] + [WRONG-docs]:
- PrimeStableNamesForCodegen docstring: collapse 41 lines of (a) wrong claims about
  closure priming (the Expr.Lambda case explicitly says 'Don't prime closure names')
  and (b) references to GetOrAssignFieldCounter (deleted in round 1) — into 11 lines
  describing what the code actually does.
- CodegenAssembly docstring: replace 8-line wrong claim ('Post-IlxGen pass that
  re-orders members into alphabetical order' — no such pass exists) with a 4-line
  accurate description.
- Inline comments in TypeDefBuilder.Close / spine-walk fold / runBatch trimmed.

SEQ=PAR FCS.dll byte-identical (verified MD5). EmittedIL 488/488, RIS 789/789,
RecordTypes 19/19, sin/abs quotation regression all pass.

(#19928)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…CodeGen comment

- DeterministicTests: collapse hardcoded File1 duplicate + 10-iteration loop
  into shared fileBody i template + 2-build equality. Uses a unique temp dir
  via Guid.NewGuid() to avoid parallel test interference. ~45 LOC saved.
  Real determinism stress is covered by eng/test-determinism.ps1 Release leg.
- p_ModuleInfo: extract stableValKey + mergeRacedFlags as named helpers,
  trim 14-line incident-narrative comment to a 6-line summary. The two
  bundled fixes (entry-sort + flag-merge) are now visibly distinct.
- delayCodeGen=true: 7-line paragraph → 1-line summary.

SEQ=PAR FCS.dll byte-identical preserved. EmittedIL 488/488, RIS 789/789,
DeterministicTests 21/21, sin/abs quotation regression all pass.

Note: Opus-high's biggest round-2 proposal (revert unconditional cctor force
by deferring the GetCurrentFields predicate into the lazyInitInfo callback)
was investigated and rejected: lazyInitInfo is iterated at end of GenImplFile
(IlxGen.fs:11246), BEFORE the parallel deferred body iter in CodegenAssembly
runs. The deferred bodies populate the fields that the predicate would
check, so moving the check into the callback wouldn't help. Opus-48 and
GPT-5.5 retracted this item for the same reason.

(#19928)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ntom revert

Branch was behind origin/main by 1 commit (9983e82, PR #19858 'Fix double
Dispose in use bindings with as patterns (#12300)'). Diff vs origin/main was
showing #19858's changes as 'reverted' by this branch — pure noise from stale
base. Auto-merge takes main's version for CheckBasics.fs/.fsi,
CheckExpressions.fs, CheckDeclarations.fs, UseBindingDisposalTests.fs, and
the matching 11.0.100.md entry, shrinking the diff by ~192 LOC.

Verified post-merge: SEQ=PAR FCS.dll byte-identical, sin/abs quotation passes,
EmittedIL 488/488 and the 7 new UseBindingDisposalTests all pass.

(#19928)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…se closure-name match

Diminishing-returns micro-compactions consensus from 5 round-3 agents:

- TypeDefBuilder.UpdateOrAddCctor: replace 10-line manual for-loop with
  ResizeArray.tryFindIndexi + match. Same semantics, smaller and clearer.
- Optimizer DetupleArgs.fs + InnerLambdasToTopLevelFuncs.fs: replace
  'List.sortWith (fun a b -> compare (key a) (key b))' with 'List.sortBy key'.
- Optimizer.p_ModuleInfo.mergeRacedFlags: drop the if-equal-then-return-same
  short-circuit; allocating the record once per pickled top val is irrelevant.
- IlxGen GetIlxClosureFreeVars: collapse Option.map + Option.defaultValue 0 +
  redundant .IsNone guard into a single match expression with a 'when' clause.

SEQ=PAR FCS.dll byte-identical preserved. EmittedIL 488/488, RIS 789/789,
sin/abs quotation regression pass.

(#19928)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…cope helper

Last round-3 micro-compactions (GPT-5.5 + Opus-xhigh consensus):

- CompilerGlobalState.fs: trim multi-paragraph rationale on
  FreshCompilerGeneratedNameInScope, StableNiceNameGenerator's Lazy<_> wrapper,
  PerFileNamingScope, PerFileClosureNameScope. Public .fsi keeps full docs;
  .fs gets concise summaries + issue link. Also slightly tighter
  EmitClosureName bucket-update (single dictionary write).
- OptimizeInputs.fs: collapse 5-line rationale + mkFileNamingScope helper
  into a one-line scopeOf and inline calls.

SEQ=PAR FCS.dll byte-identical preserved. EmittedIL 488/488, RIS 789/789,
RecordTypes 19/19, sin/abs quotation regression all pass.

Three-round compaction now at floor. Per consensus of 5 agents x 3 rounds,
remaining structural changes (CodegenFileScope.OrderKey int64 widening,
fieldSpecByRange.GetOrAdd Lazy wrap, 4-encodings-of-current-file unification)
are Tier-4 follow-ups intentionally deferred.

(#19928)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI-Tooling-Check-Bypassed Tooling check: non-fork PR, not diff-analyzed

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

Determinism: closure type names differ between --parallelcompilation- and --parallelcompilation+

1 participant